package com.nts.visionworks.business.contents.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.nts.visionworks.business.contents.domain.Contents;
import com.nts.visionworks.business.contents.domain.ContentsAttribute;
import com.nts.visionworks.business.contents.domain.ContentsElement;
import com.nts.visionworks.business.contents.domain.ContentsMedia;
import com.nts.visionworks.business.contents.domain.ContentsMediaThumbnail;
import com.nts.visionworks.business.contents.domain.ContentsStatus;
import com.nts.visionworks.business.contents.domain.ContentsTemplate;
import com.nts.visionworks.business.contents.domain.ContentsTemplateAttribute;
import com.nts.visionworks.business.contents.domain.ContentsTemplateElement;
import com.nts.visionworks.business.contents.domain.UnitElement;
import com.nts.visionworks.core.hibernate.query.NamedQueryHelper;
import com.nts.visionworks.core.hibernate.query.OrderBy;
import com.nts.visionworks.core.hibernate.query.OrderColumn;
import com.nts.visionworks.core.multipart.domain.Archive;
import com.nts.visionworks.core.utility.Pair;

/**
 * 단위 원소, 컨텐츠 템플릿, 컨텐츠에 대한 DAO를 제공한다.
 * @author Devcken
 */
@Service("contentsService")
@Transactional
public class ContentsService
{
	/**
	 * VisionWorks {@link org.hibernate.SessionFactory}를 주입받는다.
	 */
	@Resource(name="visionworksSessionFactory")
	private SessionFactory _sessionFactory;
	
	/**
	 * Region 단위 원소
	 */

	/**
	 * 단위 원소 목록을 조회한다.
	 * @return 조회된 단위 원소 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<UnitElement> listUnitElements()
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria criteria = session.createCriteria(UnitElement.class);
			
			List<UnitElement> elements = criteria.list();
			
			for (UnitElement element : elements)
			{
				Hibernate.initialize(element.getContentsTemplateElements());
			}
			
			return elements;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 단위 원소 정보를 저장한다.
	 * @param unitElement 저장할 단위 원소 정보를 설정한다.
	 * @return 주어진 단위 원소 정보가 저장되면 true, 그렇지 않으면 false가 반환된다. 
	 */
	public boolean saveUnitElement(UnitElement unitElement)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			session.saveOrUpdate(unitElement);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * EndRegion 단위 원소
	 */
	
	/**
	 * Region 컨텐츠 템플릿
	 */
	
	/**
	 * 컨텐츠 템플릿 목록을 조회한다.
	 * @return 조회된 컨텐츠 템플릿 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ContentsTemplate> listContentsTemplates()
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria templateCriteria = session.createCriteria(ContentsElement.class)
					.add(Restrictions.eq("deleted", false))
					.addOrder(Order.asc("templateName"));
			
			return templateCriteria.list();
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 템플릿 정보를 저장한다.
	 * @param template 저장하고자 하는 컨텐츠 템플릿 정보를 설정한다.
	 * @return 컨텐츠 템플릿 정보가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveContentsTemplate(ContentsTemplate template)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			// 컨텐츠 템플릿 정보를 수정하는 경우
			if (template.getTemplateId() > 0)
			{
				// 해당 컨텐츠 템플릿을 사용하는 컨텐츠의 개수를 가져온다.
				Criteria contentsCriteria = session.createCriteria(Contents.class)
						.add(Restrictions.eq("templateId", template.getTemplateId()))
						.setProjection(Projections.rowCount());
				
				int templateUsingContentsCount = (int)contentsCriteria.uniqueResult();
				
				// 컨텐츠 템플릿을 사용 중인 컨텐츠가 존재하는 경우 컨텐츠 템플릿의 이름만 변경 가능하다.
				if (templateUsingContentsCount > 0)
				{
					Criteria contentsTemplateCriteria = session.createCriteria(ContentsTemplate.class)
							.add(Restrictions.eq("templateId", template.getTemplateId()));
					
					ContentsTemplate currentTemplate = (ContentsTemplate)contentsTemplateCriteria.uniqueResult();
					
					currentTemplate.setTemplateName(template.getTemplateName());
					
					template = currentTemplate;
				}
				
				template.setUpdatedDateTime(new Date());
			}
			// 컨텐츠 템플릿 정보를 등록하는 경우
			else
			{
				template.setDeleted(false);
				template.setCreatedDateTime(new Date());
			}
			
			session.saveOrUpdate(template);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 주어진 컨텐츠 템플릿 아이디에 대해서 삭제 플래그를 설정하여 저장한다.
	 * @param templateId 삭제 플래그를 설정할 컨텐츠 템플릿 아이디를 설정한다.
	 * @return 주어진 컨텐츠 템플릿의 삭제 플래그가 설정되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean deleteContentsTemplate(Integer templateId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria templateCriteria = session.createCriteria(ContentsTemplate.class)
					.add(Restrictions.eq("templateId", templateId));
			
			ContentsTemplate template = (ContentsTemplate)templateCriteria.uniqueResult();
			
			template.setDeleted(true);
			
			session.saveOrUpdate(template);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * EndRegion 컨텐츠 템플릿
	 */
	
	/**
	 * Region 컨텐츠
	 */
	
	/**
	 * 컨텐츠 목록을 조회한다.
	 * @param searchCondition 컨텐츠 목록을 조회하기 위한 검색 조건을 설정한다.
	 * @param templateId 조회하고자 하는 컨텐츠의 컨텐츠 템플릿 아이디를 설정한다.
	 * @return 조회된 컨텐츠 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<Contents> listContents(Contents searchCondition, int templateId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria templateCriteria = session.createCriteria(Contents.class);
			
			if (templateId > 0)
			{
				templateCriteria.add(Restrictions.eq("contentsTemplate.templateId", templateId));
			}
			
			if (StringUtils.hasText(searchCondition.getContentsName()))
			{
				templateCriteria.add(Restrictions.like("contentsName", searchCondition.getContentsName(), MatchMode.ANYWHERE));
			}
			
			if (searchCondition.getContentsStatus() > 0)
			{
				templateCriteria.add(Restrictions.eq("contentsStatus", searchCondition.getContentsStatus()));
			}
			
			if (StringUtils.hasText(searchCondition.getTagString()))
			{
				templateCriteria.add(Restrictions.like("tagString", searchCondition.getTagString(), MatchMode.ANYWHERE));
			}
			
			templateCriteria.add(Restrictions.eq("deleted", false));
			
			templateCriteria.addOrder(Order.desc("createdDateTime"));
			
			return templateCriteria.list();
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 주어진 컨텐츠 정보를 저장한다.
	 * @param contents 저장하고자 하는 컨텐츠 정보를 설정한다.
	 * @return 컨텐츠가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveContents(Contents contents)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			// 컨텐츠 삭제는 다른 경로를 통해서만 가능하다.
			contents.setDeleted(false);
			
			// 컨텐츠를 수정하는 경우
			if (contents.getContentsId() > 0)
			{
				contents.setUpdatedDateTime(new Date());
			}
			// 컨텐츠를 등록하는 경우
			else
			{
				contents.setContentsStatus((short)ContentsStatus.StandBy.value);
				contents.setCreatedDateTime(new Date());
			}
			
			session.saveOrUpdate(contents);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠의 삭제 플래그를 설정한다.
	 * @param contentsId 삭제 플래그를 설정할 컨텐츠의 아이디를 설정한다.
	 * @return 컨텐츠의 삭제 플래그가 설정되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean deleteContents(long contentsId)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria contentsCriteria = session.createCriteria(Contents.class)
					.add(Restrictions.eq("contentsId", contentsId));
			
			Contents contents = (Contents)contentsCriteria.uniqueResult();
			
			contents.setDeleted(true);
			
			session.update(contents);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 단위 요소 목록을 조회한다.
	 * @param contentsId 조회하려는 컨텐츠 단위 요소에 대한 컨텐츠 아이디를 설정한다.
	 * @return 조회된 컨텐츠 단위 요소 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ContentsElement> listContentsElements(long contentsId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria contentsElementCriteria = session.createCriteria(ContentsElement.class)
					.add(Restrictions.eq("contents.contentsId", contentsId));
			
			List<ContentsElement> contentsElements = (List<ContentsElement>)contentsElementCriteria.list();
			
			return contentsElements;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 단위 요소를 저장한다.
	 * @param contentsId 컨텐츠 단위 요소에 대한 컨텐츠 아이디를 설정한다.
	 * @param templateElementId 컨텐츠 단위 요소에 대한 컨텐츠 템플릿 단위 요소 아이디를 설정한다.
	 * @param contentsElement 저장하려는 컨텐츠 단위 요소 정보를 설정한다.
	 * @return 컨텐츠 단위 요소가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveContentsElement(long contentsId, int templateElementId, ContentsElement contentsElement)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			Criteria contentsCriteria = session.createCriteria(Contents.class)
					.add(Restrictions.eq("contentsId", contentsId));
			
			Contents contents = (Contents)contentsCriteria.uniqueResult();
			
			Criteria contentsTemplateElementCriteria = session.createCriteria(ContentsTemplateElement.class)
					.add(Restrictions.eq("templateElementId", templateElementId));
			
			ContentsTemplateElement contentsTemplateElement = (ContentsTemplateElement)contentsTemplateElementCriteria.uniqueResult();
			
			// 주어진 컨텐츠 아이디와 컨텐츠 템플릿 단위 요소 아이디에 대한 컨텐츠 단위 요소 중 가장 큰 단위 요소 순서값을 가져온다.
			Criteria contentsElementCriteria = session.createCriteria(ContentsElement.class)
					.add(Restrictions.eq("contentsTemplateElement.templateElementId", templateElementId))
					.add(Restrictions.eq("contents.contentsId", contentsId))
					.setProjection(Projections.max("elementOrder"));
			
			int elementOrderMax = (int)contentsElementCriteria.uniqueResult();
			
			// 저장하려는 단위 요소의 순서가 현재 저장되어 있는 단위 요소들의 순서 최대값보다 작으면 최대값에 1을 더한 값으로 재설정한다.
			if (contentsElement.getElementOrder() < elementOrderMax)
			{
				contentsElement.setElementOrder(elementOrderMax + 1);
			}
			
			contentsElement.setContents(contents);
			contentsElement.setContentsTemplateElement(contentsTemplateElement);
			
			session.save(contentsElement);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 속성 목록을 조회한다.
	 * @param contentsId 조회하려는 컨텐츠 속성에 대한 컨텐츠 아이디를 설정한다.
	 * @return 조회된 컨텐츠 속성 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ContentsAttribute> listContentsAttributes(long contentsId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria contentsAttributeCriteria = session.createCriteria(ContentsAttribute.class)
					.add(Restrictions.eq("contents.contentsId", contentsId));
			
			List<ContentsAttribute> contentsAttributes = (List<ContentsAttribute>)contentsAttributeCriteria.list();
			
			return contentsAttributes;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 속성을 저장한다.
	 * @param templateId
	 * @param templateAttributeId 컨텐츠 속성에 대한 컨텐츠 템플릿 속성 아이디를 설정한다.
	 * @param contentsId 컨텐츠 속성에 대한 컨텐츠 아이디를 설정한다.
	 * @param contentsAttribute 저장하려는 컨텐츠 속성 정보를 설정한다.
	 * @return 컨텐츠 속성이 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveContentsAttribute(int templateId, int templateAttributeId, long contentsId, ContentsAttribute contentsAttribute)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			/*Criteria contentsTemplateCriteria = session.createCriteria(ContentsTemplate.class)
					.add(Restrictions.eq("templateId", templateId));
			
			ContentsTemplate contentsTemplate = (ContentsTemplate)contentsTemplateCriteria.uniqueResult();*/
			
			Criteria contentsCriteria = session.createCriteria(Contents.class)
					.add(Restrictions.eq("contentsId", contentsId));
			
			Contents contents = (Contents)contentsCriteria.uniqueResult();
			
			Criteria contentsTemplateAttributeCriteria = session.createCriteria(ContentsTemplateAttribute.class)
					.add(Restrictions.eq("templateAttributeId", templateAttributeId));
			
			ContentsTemplateAttribute contentsTemplateAttribute = (ContentsTemplateAttribute)contentsTemplateAttributeCriteria.uniqueResult();
			
			contentsAttribute.setContents(contents);
			contentsAttribute.setContentsTemplateAttribute(contentsTemplateAttribute);
			
			session.save(contentsAttribute);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * EndRegion 컨텐츠
	 */
	
	/**
	 * Region 컨텐츠 미디어
	 */
	
	/**
	 * 컨텐츠 미디어 목록을 조회한다.
	 * @return 컨텐츠 미디어 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<Pair<ContentsMedia, Archive>> listContentsMedia()
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Query query = NamedQueryHelper.buildOrderAndGroup(session,
					"Contents.ListContentsMedia",
					new ArrayList<OrderColumn>()
					{
						private static final long serialVersionUID = 2912805987914470893L;

						{
							add(new OrderColumn("ac.UploadedDateTime", OrderBy.Descending));
						}
					},
					null);
			
			List<Object[]> items = query.list();
			
			List<Pair<ContentsMedia, Archive>> result = new ArrayList<Pair<ContentsMedia, Archive>>();
			
			for (int index = 0; index < items.size(); index++)
			{
				Object[] item = items.get(index);
				
				Pair<ContentsMedia, Archive> pair = new Pair<ContentsMedia, Archive>((ContentsMedia)item[0], (Archive)item[1]);
				
				result.add(pair);
			}
			
			return result;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 미디어 정보를 조회한다.
	 * @param mediaId 조회하려는 컨텐츠 미디어의 아이디를 설정한다.
	 * @return 주어진 컨텐츠 미디어의 아이디에 해당하는 컨텐츠 미디어 정보가 반환된다.
	 */
	public ContentsMedia getContentsMedia(Long mediaId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria mediaCriteria = session.createCriteria(ContentsMedia.class)
					.add(Restrictions.eq("mediaId", mediaId));
			
			ContentsMedia contentsMedia = (ContentsMedia)mediaCriteria.uniqueResult();
			
			return contentsMedia;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 미디어를 저장한다.
	 * @param contentsMedia 저장하려는 컨텐츠 미디어 정보를 설정한다.
	 * @return 컨텐츠 미디어가 저장되면 true, 그렇지 않으면 false가 반환된다.
	 */
	public boolean saveContentsMedia(ContentsMedia contentsMedia)
	{
		Session session = this._sessionFactory.openSession();
		
		Transaction transaction = session.beginTransaction();
		
		try
		{
			session.save(contentsMedia);
			
			transaction.commit();
			
			return true;
		}
		catch (Exception error)
		{
			transaction.rollback();
			
			throw error;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * 컨텐츠 미디어의 섬네일 목록을 조회한다.
	 * @param mediaId 조회하려는 컨텐츠 미디어 섬네일 목록의 컨텐츠 미디어 아이디를 설정한다.
	 * @return 주어진 컨텐츠 미디어 아이디에 대한 컨텐츠 미디어 섬네일 목록이 반환된다.
	 */
	@SuppressWarnings("unchecked")
	public List<ContentsMediaThumbnail> listContentsMediaThumbnails(long mediaId)
	{
		Session session = this._sessionFactory.openSession();
		
		try
		{
			Criteria contentsMediaThumbnailCriteria = session.createCriteria(ContentsMediaThumbnail.class)
					.add(Restrictions.eq("id.mediaId", mediaId));
			
			List<ContentsMediaThumbnail> contentsMediaThumbnails = contentsMediaThumbnailCriteria.list();
			
			return contentsMediaThumbnails;
		}
		finally
		{
			if (session != null && session.isOpen())
			{
				session.close();
			}
		}
	}
	
	/**
	 * EndRegion 컨텐츠 미디어
	 */
}